let {
    log,
    info,
    dir
} = console;

let toString = Object.prototype.toString,
    isStrictArray = obj => toString.call(obj) === '[object StrictArray]',
    isObject = obj => typeof obj === 'object' && obj !== null,
    isArrayLike = obj => isFinite(obj.length) && Object.keys(obj).some(key => isFinite(key)),
    isArray = obj => Array.isArray(obj);

class StrictArray {

    constructor(filterInterface) {
        this.filterInterface = filterInterface;
        //检测参数类型
        this.detectArgsType = function () {
            let filterInterface = this.filterInterface,
                type = typeof filterInterface;
            return type === 'function' ? (newValue) => Object(newValue) instanceof filterInterface ||
                filterInterface(newValue) === true && filterInterface !== Boolean :
                (newValue) => newValue === filterInterface;
        }.call(this);
        let proxy = this.observer();
        this.init();
        return proxy;
    }

    //设置Object.prototype.toString返回值的内容
    get[Symbol.toStringTag]() {
        return 'StrictArray';
    }

    //添加迭代器接口
    *[Symbol.iterator]() {
        let i = 0;
        while (this[i] !== void 0) {
            yield this[i];
            i++;
        }
    }

    init() {
        this.length = 0; // 初始化实例长度length为0
    }

    observer() {
        let proxy = new Proxy(this, {
            get(sourceObj, prop, proxyObj) {
                return sourceObj[prop];
            },
            set: (sourceObj, prop, newValue) => {
                let flag = this.detectArgsType(newValue);
                if (flag) {
                    Reflect.set(sourceObj, prop, newValue);
                    this.addLength(1);
                    return newValue;
                }
                throw new TypeError(`The type of '${newValue}' is invalid`);
            },
            deleteProperty: (sourceObj, prop) => {
                this.addLength(-1);
                return Reflect.deleteProperty(sourceObj, prop);
            }
        });
        return proxy;
    }

    addLength(plusNum) {
        if (plusNum) {
            this.length += plusNum;
            return;
        }
        let digitReg = /\d/;
        this.length = Reflect.ownKeys(this).filter(key => digitReg.test(key)).length || 0;
    }

    pop() {
        let lastIndex = this.length - 1,
            lastItem = Reflect.get(this, lastIndex);
        Reflect.deleteProperty(this, lastIndex);
        return lastItem;
    }

    unshift(...args) {
        let len = this.length,
            argsLen = args.length,
            cloneThis = Object.assign({}, this);
        for (let i = 0; i < len; i++) {
            Reflect.deleteProperty(this, i, this[i]);
            Reflect.set(this, i + argsLen, cloneThis[i]);
        }
        for (let j = 0; j < argsLen; j++) Reflect.set(this, j, args[j]);
        return len + argsLen;
    }

    push(...args) {
        let len = this.length,
            argsLen = args.length;
        for (let i = 0; i < argsLen; i++) Reflect.set(this, len + i, args[i]);
        return len + argsLen;
    }

    forEach(cb) {
        let len = this.length;
        for (let i = 0; i < len; i++) cb(this[i], i, this);
    }

    map(cb) {
        let len = this.length,
            newStrictArray = new this.constructor(this.filterInterface);
        for (let i = 0; i < len; i++) newStrictArray.push(cb(this[i], i, this));
        return newStrictArray;
    }

    reduce(cb, total) {
        let len = this.length,
            i = 0;
        if (total === void 0) {
            i = 1;
            total = this[0];
        }
        for (; i < len; i++) total = cb(total, this[i], i, this);
        return total;
    }

    slice(beigin = 0, end = this.length) {
        let len = this.length,
            newStrictArray = new this.constructor(this.filterInterface);
        beigin < 0 && (beigin += len);
        end < 0 && (end += len);
        end > len && (end = len);
        if (beigin < end) {
            let diffLen = end - beigin;
            for (let i = 0; i < diffLen; i++) Reflect.set(newStrictArray, i, this[beigin + i]);
        }
        return newStrictArray;
    }

    flat(deepth = 1) {
        let newStrictArray = new this.constructor(this.filterInterface);
        return deepth === 0 ? this.slice(0) :
            this.reduce((total, now) => total.concat(isArrayLike(now) ? now.flat(deepth - 1) : now), newStrictArray);
    }

    filter(cb) {
        let len = this.length,
            newStrictArray = new this.constructor(this.filterInterface);
        for (let i = 0; i < len; i++) cb(this[i], i, this) && newStrictArray.push(this[i]);
        return newStrictArray;
    }

    concat(...args) {
        let argsLen = args.length,
            newStrictArray = new this.constructor(this.filterInterface);
        newStrictArray.push(...this);
        if (argsLen !== 0) {
            let multiplePush = (strictArray, addObj, deep = 1) => {
                if (deep === 0) {
                    strictArray.push(addObj);
                } else {
                    for (let i = 0; i < argsLen; i++) {
                        let curItem = addObj[i];
                        isArrayLike(curItem) ? multiplePush(strictArray, curItem, deep - 1) : strictArray.push(curItem);
                    }
                }
            }
            multiplePush(newStrictArray, args);
        }
        return newStrictArray;
    }

}